package cn.stimd.spring.beans.factory.annotation;

import cn.stimd.spring.beans.BeansException;
import cn.stimd.spring.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.ReflectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class InitDestroyAnnotationBeanPostProcessor implements DestructionAwareBeanPostProcessor, PriorityOrdered {
    protected transient Log logger = LogFactory.getLog(getClass());
    private Class<? extends Annotation> initAnnotationType;
    private Class<? extends Annotation> destroyAnnotationType;
    private int order = Ordered.LOWEST_PRECEDENCE;
    private transient final Map<Class<?>, LifecycleMetadata> lifecycleMetadataCache = new ConcurrentHashMap<>(256);

    public InitDestroyAnnotationBeanPostProcessor() {
        this.initAnnotationType = PostConstruct.class;
        this.destroyAnnotationType = PreDestroy.class;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
        try {
            metadata.invokeInitMethods(bean, beanName);
        } catch (Throwable e) {
            throw new BeansException("调用init方法失败, beanName: " + beanName, e);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        //查找init和destroy方法的元数据
        LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
        try {
            //调用销毁方法
            metadata.invokeDestroyMethods(bean, beanName);
        } catch (Throwable e) {
            // 忽略异常，继续执行其他Bean的销毁逻辑
            logger.error("调用destroy方法失败，beanName: " + beanName, e);
        }
    }

    @Override
    public boolean requiresDestruction(Object bean) {
        return findLifecycleMetadata(bean.getClass()).hasDestroyMethods();
    }

    @Override
    public int getOrder() {
        return this.order;
    }


    private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
        LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);
        if (metadata == null) {
            //构建init和destroy方法的元数据
            metadata = buildLifecycleMetadata(clazz);
            this.lifecycleMetadataCache.put(clazz, metadata);
        }
        return metadata;
    }

    private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
        LinkedList<LifecycleElement> initMethods = new LinkedList<>();
        LinkedList<LifecycleElement> destroyMethods = new LinkedList<>();
        Class<?> targetClass = clazz;

        //寻找当前类以及父类的init和destroy方法
        do{
            ReflectionUtils.doWithLocalMethods(targetClass, method -> {
                //检查方法是否声明@PostConstruct注解
                if(this.initAnnotationType != null){
                    if(method.getAnnotation(this.initAnnotationType) != null){
                        LifecycleElement element = new LifecycleElement(method);
                        initMethods.add(element);
                    }
                }

                //检查方法是否声明@PreDestroy注解
                if(this.destroyAnnotationType != null){
                    if(method.getAnnotation(this.destroyAnnotationType) != null){
                        LifecycleElement element = new LifecycleElement(method);
                        destroyMethods.add(element);
                    }
                }
            });
            targetClass = targetClass.getSuperclass();
        }while (targetClass != null && targetClass != Object.class);

        return new LifecycleMetadata(initMethods, destroyMethods);
    }


    private class LifecycleMetadata {
        private final Collection<LifecycleElement> initMethods;     //init方法集合
        private final Collection<LifecycleElement> destroyMethods;  //destroy方法集合

        public LifecycleMetadata(Collection<LifecycleElement> initMethods, Collection<LifecycleElement> destroyMethods) {
            this.initMethods = initMethods;
            this.destroyMethods = destroyMethods;
        }

        public void invokeInitMethods(Object target, String beanName) throws Throwable {
            if(!this.initMethods.isEmpty()){
                for (LifecycleElement element : this.initMethods) {
                    element.invoke(target);
                }
            }
        }

        public void invokeDestroyMethods(Object target, String beanName) throws Throwable {
            if(!this.destroyMethods.isEmpty()){
                for (LifecycleElement element : this.destroyMethods) {
                    element.invoke(target);
                }
            }
        }

        public boolean hasDestroyMethods() {
            return !destroyMethods.isEmpty();
        }
    }


    private static class LifecycleElement {
        private final Method method;

        public LifecycleElement(Method method) {
            this.method = method;
        }

        public void invoke(Object target) throws Throwable {
            ReflectionUtils.makeAccessible(this.method);
            this.method.invoke(target, (Object[]) null);
        }
    }
}
